import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.lang.Thread.State;
import java.net.Socket;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.Timer;
import java.util.Vector;
//import java.net.Socket;



public abstract class ServerConnection implements Runnable{

	public synchronized void job(){}
}

class serverReader extends ServerConnection {
	Hashtable<String, data_content> cache;
	protected Vector <Request> requestsQueue;
	DataOutputStream proxyToServer;
	Request req;
	int requestNumber;
	Socket clientSocket;
	Thread writer;
	
	public serverReader(Vector <Request> q, Hashtable<String, data_content> c, DataOutputStream pTs, Socket s, Thread w) {
		this.cache = c;
		this.requestsQueue = q;
		this.proxyToServer = pTs;
		this.requestNumber = 0;
		this.clientSocket = s;
		this.writer = w;
	}
	
	public void run() {
		// TODO Auto-generated method stub
		// Should check queue and remove request. when done with request
		//while(requestsQueue.isEmpty());
		
		System.err.println(Thread.currentThread() + ": serverReader has started");
		for(;;) {
			// Check if the socket is closed
			if(clientSocket.isClosed())
				break;
			
			if (!(requestNumber >= requestsQueue.size())){
				req = requestsQueue.get(requestNumber);
				requestNumber++;
				
				// check if the request is valid?
				if (req.getRequestHeaderValue("Bad-Request") == null) {
				
					String requestType = req.getRequestHeaderValue("Request-Type");
					String fileName = req.getRequestHeaderValue("File-Name");
					
					//check http version
					String version = req.getRequestHeaderValue("Version");
					if (requestType != null && fileName != null && version != null ){
						if (version.compareTo("HTTP/1.1") == 0){
							handleRequest(fileName, requestType);
							/*if(writer.getState().equals(State.NEW)) {
								writer.start();
							}*/
								
							
						} else if (version.compareTo("HTTP/1.0") == 0){
							
	/*						nonPersistentMode = true;
							
							if (requestType.compareTo("GET") == 0) {
								handleResponse(fileName,"GET", ifModifiedSince);
							}else if (requestType.compareTo("HEAD") == 0){
								handleResponse(fileName,"HEAD",ifModifiedSince);
							}else
								notifyClient("Error 501: "+ requestType+
										" is not implemented");*/
							
							// It is a non-persistent TCP connection, 
							//thus the connection is closed after serving a response
							//socketShouldBeClosed = true;
							notifyClient("Error 400: "+version+" is not supported.");
						} else {
							//send400Message(  "Error 400: "+version+" is not supported.", nonPersistentMode);
							notifyClient("Error 400: "+version+" is not supported.");
						}
					}else {
						notifyClient("Error 400: Bad request syntax.");
					}
				} else {
					notifyClient("Error 400: Bad request. Please check your request format.");			
				}
			}
		}
	}
	
	public void handleRequest(String fileName, String requestType) {
		
		//Check cache
		data_content entry = cache.get(fileName);
		StringTokenizer location = new StringTokenizer(fileName, "/");
		
		location.nextToken();
		String URL = location.nextToken();
		String[] URLinfo = URL.split(":");
		URL = URLinfo[0];
		
		String requestData = "[UNKNOWN MESSAGE]";
		
		//TODO: we need to make the proxy server to be able to handle chunking cached data.
		try {
			if (entry == null) {
				if(requestType.compareTo("GET") == 0) {
					String contentRange = req.getRequestHeaderValue("Range");
					if (contentRange != null)
						requestData = "GET " + fileName + " HTTP/1.1\r\n" + 
											"Host: " + URL + "\r\n" +
											"Range: " + contentRange + "\r\n" +
											"\r\n";
					else
						requestData = "GET " + fileName + " HTTP/1.1\r\n" + 
											"Host: " + URL + "\r\n" +
											"\r\n";
					writeToServer(requestData);
				} else if (requestType.compareTo("HEAD") == 0) {
					requestData = "HEAD " + fileName + " HTTP/1.1\r\n" + 
											"Host: " + URL + "\r\n" +	
											"\r\n";
					writeToServer(requestData);
				} else {
					notifyClient("Request type not supported: " + requestType);
				}
			} else {
				System.err.println("WHAT????? proxy serves non-existing object????");
				if(requestType.compareTo("GET") == 0) {
					String contentRange = req.getRequestHeaderValue("Range");
					if (contentRange != null)
						requestData = "GET " + fileName + " HTTP/1.1\r\n" + 
									"Host: " + URL + "\r\n" +
									"Range: " + contentRange + "\r\n" +
									"If-Modified-Since: " + entry.getTimestamp() + "\r\n" + 
									"\r\n";
					else 
						requestData = "GET " + fileName + " HTTP/1.1\r\n" + 
									"Host: " + URL + "\r\n" +
									"If-Modified-Since: " + entry.getTimestamp() + "\r\n" + 
									"\r\n";
					
					writeToServer(requestData);	
				} else if(requestType.compareTo("HEAD") == 0) {
					requestData = "HEAD" + fileName + " HTTP/1.1\r\n" + 
								"Host: " + URL + "\r\n" +
								"If-Modified-Since: " + entry.getTimestamp() + "\r\n" + 
								"\r\n";
					writeToServer(requestData);	
				} else {
					notifyClient("Request type not supported: " + requestType);
				}
			}
		} catch (IOException e) {
			System.out.println("ERROR! Unable to write data: " + requestData);
		}

	}

	public void writeToServer(String m) throws IOException{
		synchronized(proxyToServer){
			System.err.println("Sending request: "+ m);
			proxyToServer.writeBytes(m);
			proxyToServer.flush();
		}
	}
	public void notifyClient (String error) {
		System.out.println(error);
	}
	
	@Override
	public void job() {
		// TODO Auto-generated method stub
		
	}
	
}

class serverWriter extends ServerConnection {
	Hashtable<String, data_content> cache;
	DataInputStream serverToProxy;
	DataOutputStream proxyToClient;
	Vector <Request> requestsQueue;
	int responseNumber;
	int oldResponseNumber;
	Request req;
	Socket clientSocket;
	
	public serverWriter(Vector <Request> q, Hashtable<String, data_content> c, DataInputStream sTp, DataOutputStream pTc, Socket cs) {
		this.cache = c;
		this.serverToProxy = sTp;
		this.proxyToClient = pTc;
		this.requestsQueue = q;
		this.responseNumber = 0;
		this.oldResponseNumber = -1;
		this.clientSocket = cs;
	}
	
	public void run() {
		// TODO Auto-generated method stub
		// Write data back to client
		System.err.println(Thread.currentThread() + ": serverWriter has started");
		while(true) {
			if((requestsQueue.size()) <= responseNumber) {
				System.err.println("Waiting 120 seconds for requests to come on queue");
//				Timer t = 
				break;
			}
			else {
				req = requestsQueue.get(responseNumber);
				handleResponse();
			}
			
			
		}

		
		
	}
	
	@SuppressWarnings("deprecation")
	public void handleResponse() {
		try {
			
			String headerInfo;
			String message = serverToProxy.readLine();
			System.err.println("In handleResponse from server: message = "+message);
			if (message == null) {
				return;
			} else
				responseNumber++;
			
			StringTokenizer requestMessage = new StringTokenizer(message);
			System.err.println("requestMessage next token: "+requestMessage.nextToken());
			Request headers = new Request();
			HashMap <String, String> headerValues;
			
			headerInfo = serverToProxy.readLine();
			System.err.println("Reading message from server connection: " + headerInfo);
			//while((!(headerInfo.equals("")))) { // BS, must test what readline gives when input = \r\n
			while ( headerInfo != null
					&& !(headerInfo.equals("")) 
					&& !(headerInfo.equals("\n") )
					&& !(headerInfo.equals("\r"))
					&& !(headerInfo.equals("\r\n"))
					&& !(headerInfo.equals("\n\r") )) {
				headers.hashRequest(headerInfo);
				headerInfo = serverToProxy.readLine();
			}
			
			System.err.println("Response Header Map: "+headers.getRequestMap());
			String fileName = req.getRequestHeaderValue("File-Name");
			String messageNumber = requestMessage.nextToken();
			System.err.println(messageNumber);
			
			// 304 for HEAD??? need to have a check if it is GET or HEAD
			if (messageNumber.equals("304")) {
				System.err.println("Data from cache is being processed");
				data_content entry = cache.get(fileName);
				
				String response = "HTTP/1.1 200 Document Follows\r\n" + 
				"Host: Client\r\n" + 
				"Content-Length: " + entry.getData().length + "\r\n" + 
				"Last-Modified: " + entry.getTimestamp() +  "\r\n" + 
				"\r\n";
				
				System.err.println("Writing Respone to client: " + response);
				proxyToClient.writeBytes(response);
				proxyToClient.write(entry.getData());
				proxyToClient.flush();
			
			// HANDLE OK OR PARTIAL (206)	
			} else if (messageNumber.equals("200") || messageNumber.equals("206")) {
				
				if(req.getRequestHeaderValue("Request-Type").equals("GET"))
					handleGET(fileName, headers);
				else
					handleHEAD(fileName, headers);

	
			} else if (messageNumber.equals("404")){
				String response = "HTTP/1.1 404 Not Found\r\n" + 
				"Host: Client\r\n" +
				"\r\n";
				
				proxyToClient.writeBytes(response);
				proxyToClient.flush();
			}else {
				String modifiedMessage = message + "\r\n";
				
				headerValues = headers.getRequestMap();
				Set<String> keys = headerValues.keySet();
				Iterator<String> headerKeys = keys.iterator();
				
				while(headerKeys.hasNext()) {
					String key = headerKeys.next();
					if (key.compareTo("Host") == 0) {
						modifiedMessage.concat(key + ": " + "Client" + "\r\n");
					}
					else
						modifiedMessage.concat(key + ": " + headerValues.get(key) + "\r\n");
					
				}
				
				modifiedMessage.concat("\r\n");
				proxyToClient.writeBytes(modifiedMessage);
				proxyToClient.flush();
			}		
		} catch (IOException e) {
			
		}
	}

	@Override
	public void job() {
		// TODO Auto-generated method stub
		
	}
	
	public void handleGET (String fileName, Request headers) throws IOException {
		String slength = headers.getRequestHeaderValue("Content-Length");
		int length = Integer.parseInt(slength);
		byte[] data = new byte[length];
		
		for (int i = 0; i < length; i++) {
			data[i] = serverToProxy.readByte();
		}
		
		String date = headers.getRequestHeaderValue("Last-Modified");
		data_content cache_content = new data_content(date,data);
		
		// Check if file already in cache
		if (cache.containsKey(fileName))
			cache.remove(fileName);
		
		cache.put(fileName, cache_content);
		
		// Transfer the response message 
		// TODO: Need to add ad for html files
		
		String response = "HTTP/1.1 200 OK\r\n" + 
						"Host: Client\r\n" + 
						"Content-Length: " + length + "\r\n" + 
						"Last-Modified: " + date + "\r\n"; 
						
		String contentRange = headers.getRequestHeaderValue("Content-Range");
		
		if (contentRange != null)
			response += "Content-Range: "+contentRange + "\r\n";
		response += "\r\n";
											
		System.err.println("Writing Respone to client: " + response);
		proxyToClient.writeBytes(response);
		proxyToClient.write(data);
		proxyToClient.writeBytes("\r\n");
		proxyToClient.flush();
	}
	
	public void handleHEAD(String fileName, Request headers) throws IOException {
		String slength = headers.getRequestHeaderValue("Content-Length");
		int length = Integer.parseInt(slength);
		
		String date = headers.getRequestHeaderValue("Last-Modified");
		//data_content cache_content = new data_content(date,data);
		
		String response = "HTTP/1.1 200 OK\r\n" + 
						"Host: Client\r\n" + 
						"Content-Length: " + length + "\r\n" + 
						"Last-Modified: " + date + "\r\n"+
						"\r\n";
		
		System.err.println("Writing Respone to client: " + response);
		proxyToClient.writeBytes(response);
		proxyToClient.flush();
	}
}
